package com.example.util;

import com.sun.istack.internal.NotNull;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.*;
import org.activiti.engine.history.*;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntityImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.el.PropertyNotFoundException;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipInputStream;

/**
 * [描述]
 *@author Da.Pang
 */
@Component
public class Activiti7Util {
    /**
     * [描述]资源管理类
     */
    @Autowired
    private RepositoryService repositoryService;
    /**
     * 运行时管理类
     */
    @Autowired
    private RuntimeService runtimeService;
    /**
     * [描述] 任务管理类
     */
    @Autowired
    private TaskService taskService;
    /**
     * [描述] 历史数据管理类
     */
    @Autowired
    private HistoryService historyService;
    /**
     * [描述] 流程引擎管理类
     */
    @Autowired
    private ManagementService managementService;

    /**
     * [描述] 流程部署-- 上传文件进行部署
     * @param name：: 给部署起一个名字
     * @param file:上传的bpmn文件
     * @param bpmnResourceName: test.bpmn 文件名
     * @param imgFile: bpmn图片文件
     * @param imgResourceName: bpmn图片文件名
     */
    public Map<String, Object> importProcess(MultipartFile file ,MultipartFile imgFile, String name ,
                                    String bpmnResourceName , String imgResourceName) throws Exception{
        Resource resource = file.getResource();
        DeploymentBuilder name1 = repositoryService
                .createDeployment()
                .name(name);
        name1.addInputStream(bpmnResourceName, file.getInputStream());
        if (imgResourceName != null){
            name1.addInputStream(imgResourceName, imgFile.getInputStream());
        }
        Deployment deployment = name1.deploy();
        return getMap(deployment);
    }


    /**
     * [描述] 流程部署--将bpmn文件放在resources/process 文件夹中进行部署
     * @param name 部署时候可以起一个名字
     * @param bpmnName bpmn文件名[带文件后缀 .bpmn]
     * @param bpmnImgName bpmn图片名[带图片后缀]
     */
    public Map<String, Object> importProcessByResources(@NotNull String name , @NotNull String bpmnName , String bpmnImgName){
        DeploymentBuilder deployment = repositoryService.createDeployment();
        deployment.name(name);
        deployment.addClasspathResource("processes/"+bpmnName);
        if (bpmnImgName != null){
            deployment.addClasspathResource("processes/"+bpmnImgName);
        }
        //执行部署
        Deployment deploy = deployment.deploy();
       return getMap(deploy);
    }

    /**
     * [描述] 流程部署-- 通过zip包进行bpmn文件部署
     * @param zipPath: zip文件的路径 带[.zip] 后缀
     * @param name: 给部署起一个名字
     */
    public Map<String, Object> importProcessByZip(String zipPath , String name  ) throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(zipPath);
        ZipInputStream stream = new ZipInputStream(fileInputStream);
        Deployment deploy = repositoryService.createDeployment()
                .addZipInputStream(stream)
                //act_re_procdef 表中的name
                .name(name)
                .deploy();
       return getMap(deploy);
    }

    private Map<String, Object> getMap(Deployment deploy) {
        Map<String, Object> map = new HashMap<>(3);
        System.out.println("部署ID：" + deploy.getId());
        System.out.println("部署Name：" + deploy.getName());
        System.out.println("部署时间：" + deploy.getDeploymentTime());
        map.put("deploymentId", deploy.getId());
        map.put("deploymentName", deploy.getName());
        map.put("deploymentTime", deploy.getDeploymentTime());
        return map;
    }


    /**
     * [描述] 查询流程定义集合【流程定义模板列表】
     * @param key bpmn文件中的 processId
     *            processId = null 则查全部模板
     */
    public List<Map<String, Object>> queryProcessDefinition(String key){
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        if (key != null){
            //根据指定key查出模板
            query.processDefinitionKey(key);
        }

        List<Map<String, Object>> result = new ArrayList<>();
        //List<processDefinitionDTO> result = new ArrayList<>();
        List<ProcessDefinition> list = query.list();
        for (ProcessDefinition processDefinition : list) {
            System.out.println("流程定义id="+processDefinition.getId());
            System.out.println("流程定义名称="+processDefinition.getName());
            System.out.println("流程定义key="+processDefinition.getKey());
            System.out.println("流程定义版本="+processDefinition.getVersion());
            System.out.println("流程部署id="+processDefinition.getDeploymentId());
            Map<String, Object> map = new HashMap<>(5);
            map.put("deploymentId", processDefinition.getDeploymentId());
            map.put("processDefinitionId", processDefinition.getId());
            map.put("processDefinitionKey", processDefinition.getKey());
            map.put("processDefinitionName", processDefinition.getName());
            map.put("processDefinitionVersion", processDefinition.getVersion());
            result.add(map);
        }
        return result;
    }


    /**
     * [描述] 删除流程部署信息
     * @param deploymentId 流程部署id
     * @param cascade: true-连同已经存在的运行任务一起删除
     *                 false-不删除运行的任务
     *@author Da.Pang
     */
    public void deleteProcessDefinition(String deploymentId , boolean cascade)   {
        //传入 流程部署id【如果已经存在运行任务，则这样的删除会报错】
        //repositoryService.deleteDeployment(deploymentId);
        //即便已经存在运行的任务，会执行级联删除操作【将运行的任务一起删除】
        repositoryService.deleteDeployment(deploymentId, cascade);
        System.out.println("删除流程部署信息成功");
    }


    /**
     * [描述] 根据指定的bpmn的 processId 下载已经存在数据库中的文件
     * 需要 common-io.jar
     * @param key bpmn文件中的 processId
     * @param imgSavePath 需要把对应的图片文件保存的绝对路径【例：D:/bpmn/test-image.png】
     * @param bpmnSavePath 需要把对应的bpmn文件保存的绝对路径【例：D:/bpmn/test-bpmn.bpmn】
     */
    public void downDeployment(String key ,String imgSavePath,String bpmnSavePath) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
            //根据指定key查出模板
            query.processDefinitionKey(key);
        for (ProcessDefinition processDefinition : query.list()) {
            //    拿到deploymentId
            String deploymentId = processDefinition.getDeploymentId();
            //图片目录和名称
            String pngName = processDefinition.getDiagramResourceName();
            InputStream resourceAsStream = repositoryService.getResourceAsStream(deploymentId, pngName);
            String bpmnName = processDefinition.getResourceName();
            InputStream bpmnNameResourceAsStream = repositoryService.getResourceAsStream(deploymentId, bpmnName);
            File pngFile = new File(imgSavePath);
            File bpmnFile = new File(bpmnSavePath);
            FileOutputStream pngFileOutputStream = null;
            FileOutputStream bpmnFileOutputStream = null;
            try{
             pngFileOutputStream = new FileOutputStream(pngFile);
             bpmnFileOutputStream = new FileOutputStream(bpmnFile);
            IOUtils.copy(resourceAsStream, pngFileOutputStream);
            IOUtils.copy(bpmnNameResourceAsStream, bpmnFileOutputStream);
                }catch(Exception e){
                e.printStackTrace();
            }finally {
                //关闭流输入流
                try {
                    resourceAsStream.close();
                bpmnNameResourceAsStream.close();
                assert pngFileOutputStream != null;
                assert bpmnFileOutputStream != null;
                //关闭流输出流
                pngFileOutputStream.close();
                bpmnFileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
       System.out.println("操作成功");
    }

    /**
     * [描述] 开启一个流程实例
     * @param processDefinitionId 流程定义id
     * @param businessKey 可以绑定一个自定义的业务id
     * @param params:
     *              key:在bpmn文件中定义的变量占位符【例： ${userName}】
     *              value:对应的占位值 【例： 小明】
     *@author Da.Pang
     */
    public void startProcessInstanceByKey(String processDefinitionId, String businessKey , Map<String,Object> params ){
        // 根据流程定义ID启动流程
        ProcessInstance one = runtimeService.startProcessInstanceByKey(processDefinitionId, params);
        System.out.println("流程定义id："+one.getProcessDefinitionId());
        System.out.println("流程实例id："+one.getId());
        System.out.println("当前活动id："+one.getActivityId());
        System.out.println("当前业务id："+one.getBusinessKey());
        System.out.println(one.getProcessVariables());
        System.out.println("===============启动成功==================");
    }


    /**
     * [描述] 【如果某个任务设置了候选人】则可通过该方法查询候选人【根据人名查出 自己有哪些任务待拾取】
     *@author Da.Pang
     * @param key bpmn里面的id值
     * @param candidateUserName: 候选人名
     */
    public List<Map<String, Object>> taskListByCandidateUser( String key ,String candidateUserName){
        //获取 已经运行的任务列表
        List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(key).list();
        //List<TaskDTO> result = new ArrayList<>();
        List<Map<String, Object>> result = new ArrayList<>();
        // 根据传入的候选人名字 去查出哪些任务待办
        for (Task task : taskList) {
            System.out.println("任务ID:" + task.getId());
            System.out.println("任务名称:" + task.getName());
            System.out.println("任务创建时间:" + task.getCreateTime());
            System.out.println("任务委派人:" + task.getAssignee());
            System.out.println("流程实例ID:" + task.getProcessInstanceId());
            //根据任务id 查出相应的候选人列表
            List<IdentityLink> linksForTask = taskService.getIdentityLinksForTask(task.getId());
            for (IdentityLink link : linksForTask) {
                System.out.println("候选人："+link.getUserId());
                System.out.println("组id："+link.getGroupId());
                System.out.println("候选类型："+link.getType());
                //说明该任务的候选人之一 跟传入的参数相同 则该任务属于待办
                if (candidateUserName.equals(link.getUserId())){
                    List<String> collect = linksForTask.stream().map(IdentityLink::getUserId).collect(Collectors.toList());
                    Map<String, Object> map = new HashMap<>(8);
                    map.put("taskId", task.getId());
                    map.put("taskName", task.getName());
                    map.put("createTime", task.getCreateTime());
                    map.put("taskAssignee", task.getAssignee());
                    map.put("processInstanceId", task.getProcessInstanceId());
                    map.put("candidateList", collect);
                    map.put("candidateType", link.getType());
                    result.add(map);
                    break;
                }
            }
        }
        return result;
    }


    /**
     * [描述] 【如果某个任务设置了候选人】 某个负责人进行拾取任务【通过任务id和人名 开始拾取任务】
     *@author Da.Pang
     * @param taskId： 任务id
     * @param candidateUserName 候选人名
     * @return true-拾取成功 false=拾取失败
     */
    public boolean claimTask( String taskId , String candidateUserName ){
        //根据条件 查出任务
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                //由于这里 会调用Spring Security 框架 所有不采用这个方式
                //.taskCandidateUser(candidateUserName)
                .singleResult();
        //能查到任务并且 传入参数也符合候选人 才能进行拾取
        if (task != null && isCandidate(taskId, candidateUserName)){
            //进行拾取操作 其实就是把指定任务的assignee设置对应的负责人名
            taskService.claim(task.getId(), candidateUserName);
            System.out.println("拾取任务成功，往下可以继续完成改任务");
            return true;
        }
        System.out.println("【error】拾取任务失败，没有查到对应任务");
        return false;
    }

    /**
     * [描述] 判断指定任务的候选人名单中是否有： candidateUserName
     * @return true:是候选人  false：不是候选人
     */
    private boolean isCandidate(String taskId , String candidateUserName ){
        for (IdentityLink link : taskService.getIdentityLinksForTask(taskId)) {
            if (candidateUserName.equals(link.getUserId())){
                return true;
            }
        }
        return false;
    }

    /**
     * [描述]【如果某个任务设置了候选人】 负责人已经拾取某个任务以后 打算再归还回去
     *@author Da.Pang
     * @param taskId： 任务id
     * @param candidateUserName 候选人名
     */
    public boolean claimTaskReturn( String taskId , String candidateUserName ){
        //根据条件 查出任务
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskCandidateUser(candidateUserName)
                .singleResult();
        if (task != null){
            //归还操作 其实就是把该任务的 assignee 设置成null
            taskService.setAssignee(task.getId(), null);
           System.out.println("归还任务成功");
           return true;
        }
        System.out.println("【error】归还任务失败，没有查到任务");
        return false;
    }


    /**
     * [描述] 【如果某个任务设置了候选人】某个任务虽然是自己的  但是可以交给其他人进行完成
     *@author Da.Pang
     * @param taskId： 任务id
     * @param candidateUserName 候选人名
     * @param assigneeName： 交接人
     */
    public boolean taskHandover(String taskId , String candidateUserName, String  assigneeName  ){
        //根据条件 查出任务
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskCandidateUser(candidateUserName)
                .singleResult();
        //交接操作 这个任务是自己虽然是候选人 但是 可以直接把这个任务交接给别人进行完成
        if (task != null){
            taskService.setAssignee(task.getId(), assigneeName);
            System.out.println("交接成功");
            return true;
        }
        System.out.println("【error】交接失败，没有查到任务");
        return false;
    }


    /**
     * [描述] 根据名字 查询个人待执行的任务【已经把任务拾取了】【带分页】
     * @param taskAssignName 人名【例：班长、经理、总经理等...】
     * @param key  bpmn文件中的processId
     * @param firstResult  当前页【默认从0开始】
     * @param maxResults  页长
     */
    public  List<Map<String, Object> > taskListByMy(String taskAssignName , String key , Integer firstResult , Integer maxResults){
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(taskAssignName)
                .listPage(firstResult, maxResults);
        List<Map<String, Object> > result = new ArrayList<>();
        for (Task task : list) {
            System.out.println("流程定义id="+task.getProcessDefinitionId());
            System.out.println("任务id="+task.getId());
            System.out.println("任务负责人="+task.getAssignee());
            System.out.println("任务名称="+task.getName());
            Map<String, Object> taskMap = new HashMap<>(5);
            taskMap.put("taskId", task.getId());
            taskMap.put("taskName", task.getName());
            taskMap.put("createTime", task.getCreateTime());
            taskMap.put("taskAssignee", task.getAssignee());
            taskMap.put("processInstanceId", task.getProcessInstanceId());
            result.add(taskMap);
        }
        return result;
    }


    /**
     * [描述] 完成个人任务
     * @param taskAssignName： 任务代理人
     * @param taskId： 任务id
     * params: 流程连接线上的变量
     * 【例】假如流程线上面有一个变量名 ${audit=='通过'}
     * 则调用时候：params.put("audit","通过");
     * 这里执行时，会判断控制线的变量 从而控制流程走向
     */
    public boolean completeTask( String taskAssignName , String taskId , Map<String, Object> params){
        try{
            Task task = taskService.createTaskQuery()
                    .taskId(taskId)
                    .taskAssignee(taskAssignName)
                    .singleResult();
            if (task == null){
                System.out.println("【error】 说明根据任务id和人名 无法查到待办的指定任务");
                return false;
            }
            taskService.complete(taskId ,params);
            System.out.println("任务完成成功");
            return true;
            }catch(ActivitiException ee){
            ee.printStackTrace();
            System.out.println( "【error】流程线上已经绑定变量了："+ee.getMessage());
            return false;
            }catch(Exception e){
            e.printStackTrace();
            System.out.println("【error】 执行报错： "+e.getMessage());
             return false;
        }
    }

    /**
     * [描述] 先执行拾取任务，再进行完成任务。
     *@author Da.Pang
     */
    public boolean claimTaskAndCompleteTask(String taskId, String candidateUserName , Map<String, Object> params){
        if (this.claimTask(taskId,candidateUserName)){
            this.completeTask(candidateUserName, taskId,params);
            return true;
        };
        System.out.println( "【error】没有可以拾取的任务");
        return false;
    }


    /**
     * [描述] 根据相应的条件 查出历史的任务信息
     *@author Da.Pang
     * @param processDefinitionId： 流程定义id
     * @param processInstanceId： 流程实例id
     * @param assigneeName： 代理人名
     */
    public  List<HistoricActivityInstance> historicActivityInstanceList(String processDefinitionId , String processInstanceId,String assigneeName ){
        //例子：查询 act_hi_actinst 表中数据
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
        if (processDefinitionId != null){
            //查询条件： 根据流程定义id 拿到数据
            instanceQuery.processDefinitionId(processDefinitionId);
        }
        if (processInstanceId != null){
            //查询条件： 根据流程实例id 拿到数据
            instanceQuery.processInstanceId(processInstanceId);
        }
        if (assigneeName != null){
            instanceQuery.taskAssignee(assigneeName);
        }
        List<HistoricActivityInstance> list = instanceQuery.list();
        //总数
        long count = instanceQuery.count();
        for (HistoricActivityInstance instance : list) {
            System.out.println(instance.toString());
        }
        return list;
    }


    /**
     * [描述]  通过流程定义id --》 [挂起【暂停】] 或 [激活【启动】] 全部流程实例的执行
     *@author Da.Pang
     */
    public void suspendAllProcessInstance( String key ){
        //通过指定的key 获取单个流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).singleResult();
        //判断指定的流程定义 是否挂起 true=挂起  false=激活
        boolean suspended = processDefinition.isSuspended();
        //拿到流程定义id
        String processDefinitionId = processDefinition.getId();
        if (suspended){
            //说明原来是挂起的 则可以对流程定义和旗下的所有流程实例进行激活操作
            //    参数说明：processDefinitionId=流程定义id 、 activateProcessInstances=挂起或者激活、activationDate=执行事件
            repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
            System.out.println("对流程定义id为"+processDefinitionId+",进行[激活]操作");
        }else{
            // 说明原来是激活的 则可以对流程定义和旗下的所有流程实例进行挂起操作
            repositoryService.activateProcessDefinitionById(processDefinitionId, false, null);
            System.out.println("对流程定义id为"+processDefinitionId+",进行[挂起]操作");
        }
       System.out.println("流程定义"+processDefinition.getName()+"已经操作成功【包括关联的所有实例】");
    }


    /**
     * [描述]  针对某一个流程实例进行【挂起】或【激活】操作
     *@author Da.Pang
     */
    public void suspendProcessInstanceByProcessInstanceId( String processInstanceId ){
        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        //原来是挂起的
        if (instance.isSuspended()){
            runtimeService.activateProcessInstanceById(instance.getId());
            System.out.println("流程实例id="+instance.getId()+"，已激活");
        }else{
            runtimeService.suspendProcessInstanceById(instance.getId());
            System.out.println("流程实例id="+instance.getId()+"，已挂起");
        }
        System.out.println("单个流程实例："+instance.getName()+"，已经操作成功");
    }


    /**
     * [描述] 根据流程实例id【未完成】的高亮图
     *@author Da.Pang
     */
    public void xx(@NotNull String processInstanceId, HttpServletResponse response)
            throws IOException {
        ExecutionEntity pi = (ExecutionEntity) runtimeService
                .createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (pi == null) {
        //pi 如果为null  说明指定实例已经完成。可以从历史汇总查出来。
            xx2(processInstanceId, response);
            return;
        }
        ProcessDefinitionEntity pde = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                .getDeployedProcessDefinition(pi.getProcessDefinitionId());
        List<String> highLightedActivities = getHighLightedActivities(processInstanceId);
        List<String> highLightedFlows = runtimeService.getActiveActivityIds(processInstanceId);
        if (pde != null && pde.isGraphicalNotationDefined()) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(pde.getId());
            ProcessDiagramGenerator ge = new DefaultProcessDiagramGenerator();
            InputStream resource = getResource(highLightedActivities, highLightedFlows, bpmnModel, ge);
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = resource.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
        } else {
            throw new ActivitiException("Process instance with id "
                    + processInstanceId + " has no graphic description");
        }
    }

    /**
     * [描述] 根据流程实例id【完成】的高亮图
     *@author Da.Pang
     */
    private void xx2(String processInstanceId, HttpServletResponse response)throws IOException {
        HistoricProcessInstanceEntityImpl pi = (HistoricProcessInstanceEntityImpl)historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (pi == null){
            throw new ActivitiObjectNotFoundException(
                    "Process instance with id" + processInstanceId
                            + " could not be found", ProcessInstance.class);
        }
        ProcessDefinitionEntity pde = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                .getDeployedProcessDefinition(pi.getProcessDefinitionId());
        List<String> highLightedActivities = getHighLightedActivities(processInstanceId);
        if (pde != null && pde.isGraphicalNotationDefined()) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(pde.getId());
            ProcessDiagramGenerator ge = new DefaultProcessDiagramGenerator();
            List<String> highLightedFlows = historyService.getHistoricIdentityLinksForProcessInstance(processInstanceId).stream()
                                    .map(HistoricIdentityLink::getTaskId).collect(Collectors.toList());
            InputStream resource = getResource(highLightedActivities, highLightedFlows, bpmnModel, ge);
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = resource.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
        } else {
            throw new ActivitiException("Process instance with id "
                    + processInstanceId + " has no graphic description");
        }
    }


    private List<String> getHighLightedActivities(@NotNull String processInstanceId) {
        List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).list();
        return highLightedActivitList.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
    }
    private InputStream getResource(List<String> highLightedActivities, List<String> highLightedFlows, BpmnModel bpmnModel, ProcessDiagramGenerator ge) {
        return ge.generateDiagram(bpmnModel, highLightedActivities,
                highLightedFlows,
                "宋体", "宋体", null, false);
    }

}

